// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using System;
using SharpCifs.Util;
using SharpCifs.Util.Sharpen;

namespace SharpCifs.Smb
{
	/// <summary>To filter 0 len updates and for debugging</summary>
	public class SigningDigest 
	{
		internal static LogStream Log = LogStream.GetInstance();

		private MessageDigest _digest;

		private byte[] _macSigningKey;

		private bool _bypass;

		private int _updates;

		private int _signSequence;

		/// <exception cref="SharpCifs.Smb.SmbException"></exception>
		public SigningDigest(byte[] macSigningKey, bool bypass)
		{
			try
			{
				_digest = MessageDigest.GetInstance("MD5");
			}
			catch (NoSuchAlgorithmException ex)
			{
				if (Log.Level > 0)
				{
					Runtime.PrintStackTrace(ex, Log);
				}
				throw new SmbException("MD5", ex);
			}
			this._macSigningKey = macSigningKey;
			this._bypass = bypass;
			_updates = 0;
			_signSequence = 0;
			if (Log.Level >= 5)
			{
				Log.WriteLine("macSigningKey:");
				Hexdump.ToHexdump(Log, macSigningKey, 0, macSigningKey.Length);
			}
		}

		/// <exception cref="SharpCifs.Smb.SmbException"></exception>
		public SigningDigest(SmbTransport transport, NtlmPasswordAuthentication auth)
		{
			try
			{
				_digest = MessageDigest.GetInstance("MD5");
			}
			catch (NoSuchAlgorithmException ex)
			{
				if (Log.Level > 0)
				{
					Runtime.PrintStackTrace(ex, Log);
				}
				throw new SmbException("MD5", ex);
			}
			try
			{
                switch (SmbConstants.LmCompatibility)
				{
					case 0:
					case 1:
					case 2:
					{
						_macSigningKey = new byte[40];
						auth.GetUserSessionKey(transport.Server.EncryptionKey, _macSigningKey, 0);
						Array.Copy(auth.GetUnicodeHash(transport.Server.EncryptionKey), 0, _macSigningKey
							, 16, 24);
						break;
					}

					case 3:
					case 4:
					case 5:
					{
						_macSigningKey = new byte[16];
						auth.GetUserSessionKey(transport.Server.EncryptionKey, _macSigningKey, 0);
						break;
					}

					default:
					{
						_macSigningKey = new byte[40];
						auth.GetUserSessionKey(transport.Server.EncryptionKey, _macSigningKey, 0);
						Array.Copy(auth.GetUnicodeHash(transport.Server.EncryptionKey), 0, _macSigningKey
							, 16, 24);
					    break;
					}
				}
			}
			catch (Exception ex)
			{
				throw new SmbException(string.Empty, ex);
			}
			if (Log.Level >= 5)
			{
                Log.WriteLine("LM_COMPATIBILITY=" + SmbConstants.LmCompatibility);
				Hexdump.ToHexdump(Log, _macSigningKey, 0, _macSigningKey.Length);
			}
		}

		public virtual void Update(byte[] input, int offset, int len)
		{
			if (Log.Level >= 5)
			{
				Log.WriteLine("update: " + _updates + " " + offset + ":" + len);
				Hexdump.ToHexdump(Log, input, offset, Math.Min(len, 256));
				Log.Flush();
			}
			if (len == 0)
			{
				return;
			}
			_digest.Update(input, offset, len);
			_updates++;
		}

		public virtual byte[] Digest()
		{
			byte[] b;
			b = _digest.Digest();
			if (Log.Level >= 5)
			{
				Log.WriteLine("digest: ");
				Hexdump.ToHexdump(Log, b, 0, b.Length);
				Log.Flush();
			}
			_updates = 0;
			return b;
		}

		/// <summary>Performs MAC signing of the SMB.</summary>
		/// <remarks>
		/// Performs MAC signing of the SMB.  This is done as follows.
		/// The signature field of the SMB is overwritted with the sequence number;
		/// The MD5 digest of the MAC signing key + the entire SMB is taken;
		/// The first 8 bytes of this are placed in the signature field.
		/// </remarks>
		/// <param name="data">The data.</param>
		/// <param name="offset">The starting offset at which the SMB header begins.</param>
		/// <param name="length">The length of the SMB data starting at offset.</param>
		internal virtual void Sign(byte[] data, int offset, int length, ServerMessageBlock
			 request, ServerMessageBlock response)
		{
			request.SignSeq = _signSequence;
			if (response != null)
			{
				response.SignSeq = _signSequence + 1;
				response.VerifyFailed = false;
			}
			try
			{
				Update(_macSigningKey, 0, _macSigningKey.Length);
                int index = offset + SmbConstants.SignatureOffset;
				for (int i = 0; i < 8; i++)
				{
					data[index + i] = 0;
				}
				ServerMessageBlock.WriteInt4(_signSequence, data, index);
				Update(data, offset, length);
				Array.Copy(Digest(), 0, data, index, 8);
				if (_bypass)
				{
					_bypass = false;
					Array.Copy(Runtime.GetBytesForString("BSRSPYL "), 0, data, index, 
						8);
				}
			}
			catch (Exception ex)
			{
				if (Log.Level > 0)
				{
					Runtime.PrintStackTrace(ex, Log);
				}
			}
			finally
			{
				_signSequence += 2;
			}
		}

		/// <summary>Performs MAC signature verification.</summary>
		/// <remarks>
		/// Performs MAC signature verification.  This calculates the signature
		/// of the SMB and compares it to the signature field on the SMB itself.
		/// </remarks>
		/// <param name="data">The data.</param>
		/// <param name="offset">The starting offset at which the SMB header begins.</param>
		/// <param name="length">The length of the SMB data starting at offset.</param>
		internal virtual bool Verify(byte[] data, int offset, ServerMessageBlock response
			)
		{
			Update(_macSigningKey, 0, _macSigningKey.Length);
			int index = offset;
            Update(data, index, SmbConstants.SignatureOffset);
            index += SmbConstants.SignatureOffset;
			byte[] sequence = new byte[8];
			ServerMessageBlock.WriteInt4(response.SignSeq, sequence, 0);
			Update(sequence, 0, sequence.Length);
			index += 8;
			if (response.Command == ServerMessageBlock.SmbComReadAndx)
			{
				SmbComReadAndXResponse raxr = (SmbComReadAndXResponse)response;
				int length = response.Length - raxr.DataLength;
                Update(data, index, length - SmbConstants.SignatureOffset - 8);
				Update(raxr.B, raxr.Off, raxr.DataLength);
			}
			else
			{
                Update(data, index, response.Length - SmbConstants.SignatureOffset - 8);
			}
			byte[] signature = Digest();
			for (int i = 0; i < 8; i++)
			{
                if (signature[i] != data[offset + SmbConstants.SignatureOffset + i])
				{
					if (Log.Level >= 2)
					{
						Log.WriteLine("signature verification failure");
						Hexdump.ToHexdump(Log, signature, 0, 8);
                        Hexdump.ToHexdump(Log, data, offset + SmbConstants.SignatureOffset, 8);
					}
					return response.VerifyFailed = true;
				}
			}
			return response.VerifyFailed = false;
		}

		public override string ToString()
		{
            return "LM_COMPATIBILITY=" + SmbConstants.LmCompatibility + " MacSigningKey=" + Hexdump.ToHexString
				(_macSigningKey, 0, _macSigningKey.Length);
		}
	}
}
